package emu.decvices.ppu;

import emu.decvices.bus.BusDevice;
import emu.decvices.cartridge.mappers.Mapper;
import emu.decvices.clock.ClockDevice;
import emu.decvices.cpu.DmaDevice;

import java.awt.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class PPU extends BusDevice implements ClockDevice {
    private ControlRegister register = new ControlRegister();//PPU内部寄存器，用于于CPU通讯
    private transient TVScreen tvScreen;//连接的电视机

    private int scanLine = 241;//屏幕上的扫描线
    private int cycle;//屏幕中的哪一列

    private boolean nmi;//是否处于不可屏蔽中断

    private OAM[] oamList = new OAM[64];
    private DMA dma = new DMA();

    /*
        从低位到高位
        bit0-1，选取 NameTable，00-0x2000, 01-0x2400, 10-0x2800, 11-0x2C00
        bit2，有个专门的寄存器记录访问 VRAM 时的地址，每次访问现存 VRAM，这个值都要增长，0：增长 1 即水平移动，1：增长 32 即纵向移动
        bit3，精灵使用哪个 PatternTable，0：0x0000, 1：0x1000
        bit4，背景使用哪个 PatternTable，0：0x0000, 1：0x1000
        bit5，精灵大小，0：$8\times8$，1：$8\times16$
        bit7，在 V_Blank 开始的时候产生 NMI
    */
    private Control ctrl=new Control();//控制寄存器
    /*
        从低位到高位
        bit0-1，选取 NameTable，00-0x2000, 01-0x2400, 10-0x2800, 11-0x2C00
        bit2，有个专门的寄存器记录访问 VRAM 时的地址，每次访问现存 VRAM，这个值都要增长，0：增长 1 即水平移动，1：增长 32 即纵向移动
        bit3，精灵使用哪个 PatternTable，0：0x0000, 1：0x1000
         it4，背景使用哪个 PatternTable，0：0x0000, 1：0x1000
        bit5，精灵大小，0：$8\times8$，1：$8\times16$
        bit7，在 V_Blank 开始的时候产生 NMI
    */
    private Mask mask=new Mask();//mask&status
    /**
     *
     */
    private Status status = new Status();

    /*
        PPU内部寄存器，不可被外部访问
        LoopyRegister 15位
        yyy NN YYYYY XXXXX
        ||| || ||||| +++++--------coarse X scroll   粗滚动X
        ||| || +++++--------------coarse Y scroll   粗滚动Y
        ||| ++--------------------nametable select  选取的名称表
        +++-----------------------fine Y scroll     精细滚动
     */
    private LoopyRegister vramAddress = new LoopyRegister();//v
    private LoopyRegister tramAddress = new LoopyRegister();//t

    private int fine_X;//x   单独3bit存放

    private int ppu_data_buffer = 0x00;


    //调色板
    private Color[] palScreen;


    //每个扫描线每8个时钟周期位一个循环，加载下一个瓦片(8x8像素图块)信息
    private int bg_next_tile_id=0;//下一个瓦片ID，cycle1-2   低八位有效数据
    private int bg_next_tile_attr_id=0;//下一个瓦片使用的调色板ID cycle3-4   低八位有效数据

    private int bg_next_tile_lo = 0;//下一个瓦片的该行8像素低字节位 cycle5-6  低八位有效数据

    private int bg_next_tile_hi = 0;//下一个瓦片的该行8像素高字节位 cycle7-8 低八位有效数据

    //移位寄存器
    private int bg_shift_pattern_lo = 0;//2个瓦片(当前绘制像素所在瓦片在高8位，下一个瓦片在低八位)该行8像素低字节   低16位有效数据
    private int bg_shift_pattern_hi = 0;

    private int bg_shift_attr_lo = 0;//瓦片一行8像素对应的调色盘id
    private int bg_shift_attr_hi = 0;

    private int[] sprites_pattern_lo = new int[8];//一行8个精灵，每个精灵8像素低字节
    private int[] sprites_pattern_hi = new int[8];//一行8个精灵，每个精灵8像素低字节

    private List<OAM> nextScanSpriteOAM = new ArrayList();//下一行要绘制的
    private List<OAM> scanSpriteOAM = new ArrayList<>();//当前行绘制
    private int oamListIndex=0;
    private void loadSpriteByScanLine(){
        for(;oamListIndex<oamList.length;oamListIndex++){
            OAM oam = oamList[oamListIndex];
            if(scanLine-oam.y>=0&&scanLine-oam.y<(ctrl.getValue(Control.spriteSize)==0?8:16)){
                if(nextScanSpriteOAM.size()==8){
                    status.setValue(Status.spriteOverflow,1);
                    break;
                }
                nextScanSpriteOAM.add(oam);
                oamListIndex++;
                break;//一次只取一个
            }
        }
    }
    private int loadIndex=0;

    /**
     * 加载下一行扫描线上精灵的一行像素数据
     */
    private void loadSprite(){
        if(loadIndex==nextScanSpriteOAM.size()){//第9个时钟将loadIndex归0，为下一行准备
            return;
        }
        OAM oam = nextScanSpriteOAM.get(loadIndex);
        if(ctrl.getValue(Control.spriteSize)==0){//8x8
            if((oam.attr&0x80)==0) {
                sprites_pattern_lo[loadIndex] = fromBusRead((ctrl.getValue(Control.patternSprite)<<12)+(oam.id<<4)+(scanLine-oam.y));//0x1000*patternSprite+oam.id*16
                sprites_pattern_hi[loadIndex] = fromBusRead((ctrl.getValue(Control.patternSprite)<<12)+(oam.id<<4)+(scanLine-oam.y)+8);//0x1000*patternSprite+oam.id*16
            }else{//垂直反转
                sprites_pattern_lo[loadIndex] = fromBusRead((ctrl.getValue(Control.patternSprite)<<12)+(oam.id<<4)+(7-(scanLine-oam.y)));
                sprites_pattern_hi[loadIndex] = fromBusRead((ctrl.getValue(Control.patternSprite)<<12)+(oam.id<<4)+(7-(scanLine-oam.y))+8);
            }
        }else{//8x16
            if((oam.attr&0x80)==0) {
                if(scanLine-oam.y<8){//上半部分
                    sprites_pattern_lo[loadIndex] = fromBusRead(((oam.id&0x01)<<12)+((oam.id&0xFE)<<4)+(scanLine-oam.y));//0x1000*patternSprite+oam.id*16
                    sprites_pattern_hi[loadIndex] = fromBusRead(((oam.id&0x01)<<12)+((oam.id&0xFE)<<4)+(scanLine-oam.y)+8);//0x1000*patternSprite+oam.id*16
                }else{//下半部分
                    sprites_pattern_lo[loadIndex] = fromBusRead(((oam.id&0x01)<<12)+((oam.id&0xFE)+1<<4)+(scanLine-8-oam.y));//0x1000*patternSprite+oam.id*16
                    sprites_pattern_hi[loadIndex] = fromBusRead(((oam.id&0x01)<<12)+((oam.id&0xFE)+1<<4)+(scanLine-8-oam.y)+8);//0x1000*patternSprite+oam.id*16
                }
            }else{//垂直反转
                if(scanLine-oam.y<8){//上半部分
                    sprites_pattern_lo[loadIndex] = fromBusRead(((oam.id&0x01)<<12)+((oam.id&0xFE)+1<<4)+(7-(scanLine-oam.y)));
                    sprites_pattern_hi[loadIndex] = fromBusRead(((oam.id&0x01)<<12)+((oam.id&0xFE)+1<<4)+(7-(scanLine-oam.y))+8);
                }else{//下半部分
                    sprites_pattern_lo[loadIndex] = fromBusRead(((oam.id&0x01)<<12)+((oam.id&0xFE)<<4)+(7-(scanLine-8-oam.y)));
                    sprites_pattern_hi[loadIndex] = fromBusRead(((oam.id&0x01)<<12)+((oam.id&0xFE)<<4)+(7-(scanLine-8-oam.y))+8);
                }
            }
        }
        if((oam.attr&0x40)!=0){//水平反转
            sprites_pattern_lo[loadIndex] = flipBit(sprites_pattern_lo[loadIndex]);
            sprites_pattern_hi[loadIndex] = flipBit(sprites_pattern_hi[loadIndex]);
        }
        loadIndex++;
    }

    /**
     * 翻转输入整数的二进制位。
     * 该方法将输入整数的每一位与其相邻位进行翻转，即将低位与高位互换，相邻的低位与高位互换，最终得到翻转后的结果。
     *
     * @param b 输入的整数，将对其二进制位进行翻转。
     * @return 返回翻转后的整数。确保结果在8位范围内。
     */
    private int flipBit(int b){
        // 将每四位作为一个整体，进行上下位交换
        b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
        // 每两位作为一个整体，进行上下位交换
        b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
        // 每一位与相邻位进行交换
        b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
        // 确保结果只取低8位
        return b&0xFF;
    }

    /**
     * 时钟周期操作
     */
    public void clock(){

        if(scanLine>=-1 && scanLine<240) {
            if (scanLine == 0 && cycle == 0) {
                //skipped on BG+odd
                cycle = 1;
            }
            if(cycle==0){
                oamListIndex=0;
                loadIndex=0;
                scanSpriteOAM.clear();
                scanSpriteOAM.addAll(nextScanSpriteOAM);
                nextScanSpriteOAM.clear();
            }
            if (scanLine == -1 && cycle == 1) {//扫描线处于屏幕左上角(屏幕外)
                status.setValue(Status.vBlank, 0);
                status.setValue(Status.spriteOverflow,0);
                status.setValue(Status.sprite0Hit,0);
            }

            if(cycle>=2&&cycle<258||cycle>=321&&cycle<338){
                updateShift();//移动移位寄存器
                //每8个cycle渲染一个tile的一行，cycle从1开始处理第一像素，1-8时1一个周期，9-16一个周期
                switch ((cycle-1)%8){
                    case 0://cycle 处于一个周期中的第一个clock
                        loadBgShift();//加载下一个tile信息到移位寄存器
                        /*
                        将下一个图块id加载进来，只用到vramAddress中的011111100000 (nn yyyyy xxxxx)
                                                                                0-31  0-31
                        每个图块在nametable中顺序存放，一行32个(0-31)一共32行(0-31)
                        一共有4个nametable，分别对应00,01,10,11 而都是相对0x2000(第一个nametable的ppu总线地址)这个位置的
                        例如:
                        00 11000 00111
                            24   7       相当与第一个nametable的25行第8个图块
                        对应的16进制地址:2307      第一个图块地址范围:0x2000--0x23FF

                        01 11000 00111   第二个nametable的25行第8个图块
                        对应的16进制地址:2707      第二个图块地址范围:0x2400--0x2800

                        */

                        bg_next_tile_id = fromBusRead(0x2000|(vramAddress.get()&0xFFF));
                        break;
                    //忽略case1,因为这两个cycle用来加载下一个tile信息,这里合成1个，第二个cycle直接过

                    case 2:
                        /*
                            0x23C0是第一个调色盘id地址
                            一行32个共两行64个调色盘id。
                            第一个调色盘对应屏幕左上4X4个tile组成的大图块，这16个tile公用这个调色盘
                            可以查看[调色盘布局.png]来理解

                            0x23C0:
                            0010 0011 1100 0000

                            nametable:第三个，对应10(YX)
                            coarse_Y:01000              coarseX和Y表示这个图块是第9行第5个        VV
                            coarse_X:00100              它应当使用第18个调色盘的低2位       00000000


                            coarse_Y:>>2   00010       <<3  10000
                            coarse_X:>>2   00001
                            0010 0011 1100 0000        23C0
                                 1000 0001 0001
                                 YX
                            0010 1011 1101 0001       2BD1

                            第三个调色盘从2BC0开始为第一个调色盘，对应的18号调色盘地址 2BD1
                         */

                        bg_next_tile_attr_id = fromBusRead(0x23C0|(vramAddress.nametable_Y<<11)
                                                                        |(vramAddress.nametable_X<<10)
                                                                        |((vramAddress.coarse_Y>>2)<<3)
                                                                        |(vramAddress.coarse_X>>2));

                        /*
                            得到图块使用的调色盘ID后，在找到该图块在调色盘中的ID
                            假设2BC0位置第18个调色盘ID:01001011               --------
                                                         ^^
                                                  使用这个调色盘ID

                            // +----+----+			    +----+----+
                            // | TL | TR |			    | 11 | 10 |      每个小块2X2个tile
                            // +----+----+     =        +----+----+
                            // | BL | BR |			    | 00 | 01 |
                            // +----+----+			    +----+----+
                         */
                        if(vramAddress.coarse_Y%4>=2){//下半边
                           bg_next_tile_attr_id>>=4;//只看高4位
                        }
                        if(vramAddress.coarse_X%4>=2){//是否是右半边
                            bg_next_tile_attr_id>>=2;
                        }
                        bg_next_tile_attr_id&=0x03;//只要低2位即可
                        break;

                    case 4:
                        //加载该图块这一行8像素的低字节
                        bg_next_tile_lo = fromBusRead(
                                (ctrl.getValue(Control.patternBackground)<<12) +
                                        (bg_next_tile_id<<4) + //每个图块16个字节，因此找到对应第几个图块要乘以16
                                        vramAddress.fine_Y);

                        break;
                    case 6:
                        bg_next_tile_hi = fromBusRead(
                                (ctrl.getValue(Control.patternBackground)<<12) +
                                        (bg_next_tile_id<<4) +
                                        vramAddress.fine_Y + 8
                        );
                        break;
                    case 7://水平滚动计算         //已经渲染完一个图块的8像素
                        incrementScrollX();
                        break;
                }
            }
            if(cycle>64&&cycle<=72){//加载下一行精灵
                loadSpriteByScanLine();
            }
            if(cycle>=261&&cycle<270){//用8个时钟读取8个精灵的信息
                loadSprite();

            }
            if(cycle==256){//已经渲染完毕一行
                incrementScrollY();
            }

            if(cycle==257){
                loadBgShift();
                transferAddressX();
            }

            if(cycle==260&&scanLine<=240){
                mapper.onScanLine();
            }



            if(cycle==338||cycle==340){
                bg_next_tile_id = fromBusRead(0x2000|(vramAddress.get()&0xFFF));
            }

            if(scanLine==-1&&cycle>=280&&cycle<305){
                transferAddressY();
            }
        }
        if(scanLine>=241&&scanLine<261) {
            if (scanLine == 241 && cycle == 1) {//扫描线超过屏幕后在电视左下角第一个像素位置。VBlank时间
                status.setValue(Status.vBlank, 1);
                tvScreen.repaint();
                if (ctrl.getValue(Control.generateNMI) == 1) {
                    nmi = true;
                }
            }
        }

        int pixel = 0;//最终绘制的像素
        int palette = 0;//最终绘制像素使用的调色盘
        int bg_pixel = 0;//背景像素
        int bg_palette = 0;//背景调色板id，背景共4个调色板
        int fg_pixel = 0;//精灵像素
        int fg_palette = 0;//精灵调色板id
        boolean fgPriority = false;//是否精灵优先
        //是否绘制背景
        if(mask.getValue(Mask.showBackground)==1){
            int bit_mux = 0x8000>>fine_X;
            int pix_lo = (bg_shift_pattern_lo&bit_mux)>0?1:0;
            int pix_hi = (bg_shift_pattern_hi&bit_mux)>0?1:0;
            bg_pixel = (pix_hi<<1)|pix_lo;
            int pal_lo = (bg_shift_attr_lo&bit_mux)>0?1:0;
            int pal_hi = (bg_shift_attr_hi&bit_mux)>0?1:0;
            bg_palette = (pal_hi<<1)|pal_lo;
        }

        //是否绘制精灵
        if(mask.getValue(Mask.showSprites)==1){
            //获取所有在当前行有像素的精灵
            for(int i=0;i<scanSpriteOAM.size();i++) {
                OAM oam = scanSpriteOAM.get(i);
                if (cycle - 1 - oam.x >= 0&&cycle - 1 - oam.x<8) {
                    if(fg_pixel==0) {//scanSpriteOAM显示像素的优先级为靠前的元素优先级高,当前像素已经有精灵使用,则后续精灵对应该像素颜色忽略
                        //精灵像素
                        fg_pixel = ((((sprites_pattern_hi[i] & 0x80) > 0 ? 1 : 0) << 1) | ((sprites_pattern_lo[i] & 0x80) > 0 ? 1 : 0)) & 0x03;
                        //前4个调色盘是背景的，所以要+4
                        fg_palette = (oam.attr & 0x03) + 0x04;
                        //该精灵像素有颜色
                        if (fg_pixel>0) {
                            if(mask.getValue(Mask.showBackground)==1) {
                                if (i == 0 && oam == oamList[0]) {//0号精灵
                                    if(mask.getValue(Mask.showLeftBackground)==1|mask.getValue(Mask.showLeftSprites)==1){
                                        if(cycle>=1&&cycle<258){
                                            status.setValue(Status.sprite0Hit,1);
                                        }
                                    }else{
                                        if(cycle>=9&&cycle<258){
                                            status.setValue(Status.sprite0Hit,1);
                                        }
                                    }
                                }
                            }
                            if((oam.attr&0x20)==0) {//需要在背景前面显示
                                fgPriority = true;
                            }
                        }
                    }
                    //所有该像素有颜色的精灵都移动为下个像素做准备
                    sprites_pattern_lo[i] <<= 1;
                    sprites_pattern_hi[i] <<= 1;
                }
            }
        }
        if(fg_pixel>0&&bg_pixel==0){
            pixel = fg_pixel;
            palette = fg_palette;
        }else if(fg_pixel==0&&bg_pixel>0){
            pixel = bg_pixel;
            palette = bg_palette;
        }else if(fg_palette>0&&bg_pixel>0){
            if(fgPriority){
                pixel = fg_pixel;
                palette = fg_palette;
            }else{
                pixel = bg_pixel;
                palette = bg_palette;
            }
        }

        //向屏幕指定位置绘制指定像素
        tvScreen.setPixel(cycle-1,scanLine,getColor(palette,pixel));

        cycle++;
        if (cycle>=341){//每256周期，扫描线换下一行
            cycle = 0;
            scanLine++;
            if(scanLine>=261){
                scanLine = -1;
            }
        }
    }
    private Color getColor(int paletteIndex,int pixel){
        int colorIndex = fromBusRead(0x3F00+(paletteIndex*4)+pixel);
        colorIndex&=mask.getValue(Mask.grayscale)==1?0x30:0x3F;//是否显示为黑白色
        return  palScreen[colorIndex];

    }
    /**
     * 更新移位寄存器，每个cycle都要移动一次，为了绘制对应像素
     */
    private void updateShift(){
        if(mask.getValue(Mask.showBackground)==1){
            bg_shift_pattern_lo<<=1;
            bg_shift_pattern_hi<<=1;

            bg_shift_attr_lo<<=1;
            bg_shift_attr_hi<<=1;

        }
    }

    /**
     * 加载下一个tile的数据到背景移位寄存器，每8个cycle进行一次
     */
    private void loadBgShift(){
        bg_shift_pattern_lo = (bg_shift_pattern_lo&0xFF00)|(bg_next_tile_lo&0x00FF);
        bg_shift_pattern_hi = (bg_shift_pattern_hi&0xFF00)|(bg_next_tile_hi&0x00FF);
        /*
            背景的调色板只有4个，因此2位2进制即可表达，并且每个图块所有像素都是用同一个调色版，因此，寄存器每一位对应1个像素时，调色板id都应当一致

            例如
            bg_next_tile_attr_id = 2 (ob11,2进制为10)

            lo[11111111 00000000]    (bg_next_tile_attr_id&0b01)==0b01?0xFF:0x00-->00000000  0xFF
            hi[00000000 11111111]    (bg_next_tile_attr_id&0b10)==0b10?0xFF:0x00-->11111111  0xFF
                        ^^^^^^^^
                        下个绘制的
                        tile使用的
                        调色板信息

            说名正在绘制的8个像素所有所用的调色班都是01   而下一个正在绘制的8个像素使用的调色板10
         */
        bg_shift_attr_lo = (bg_shift_attr_lo&0xFF00)|((bg_next_tile_attr_id&0b01)==0b01?0xFF:0x00);
        bg_shift_attr_hi = (bg_shift_attr_hi&0xFF00)|((bg_next_tile_attr_id&0b10)==0b10?0xFF:0x00);

    }


    private void incrementScrollX(){
        if(mask.getValue(Mask.showBackground)==1||mask.getValue(Mask.showSprites)==1){
            if(vramAddress.coarse_X==31){
                vramAddress.coarse_X=0;
                vramAddress.nametable_X=(~vramAddress.nametable_X)&0x01;
            }else{
                vramAddress.coarse_X++;
            }
        }

    }
    private void incrementScrollY(){
        if(mask.getValue(Mask.showBackground)==1||mask.getValue(Mask.showSprites)==1){
            if(vramAddress.fine_Y<7) {
                vramAddress.fine_Y++;
            }else{
                vramAddress.fine_Y=0;
                if(vramAddress.coarse_Y<29){
                    vramAddress.coarse_Y++;
                } else if(vramAddress.coarse_Y==29){
                    vramAddress.coarse_Y=0;
                    vramAddress.nametable_Y=(~vramAddress.nametable_Y)&0x01;
                }
            }
        }
    }

    private void transferAddressY(){
        if(mask.getValue(Mask.showBackground)==1||mask.getValue(Mask.showSprites)==1){
            vramAddress.fine_Y=tramAddress.fine_Y;
            vramAddress.nametable_Y=tramAddress.nametable_Y;
            vramAddress.coarse_Y=tramAddress.coarse_Y;
        }
    }
    private void transferAddressX(){
        if(mask.getValue(Mask.showBackground)==1||mask.getValue(Mask.showSprites)==1){
            vramAddress.nametable_X=tramAddress.nametable_X;
            vramAddress.coarse_X=tramAddress.coarse_X;
        }
    }

    public PPU(){
        initPalScreen();//初始化调色板
        initOAMList();//初始化OAM数组
    }

    private void initOAMList(){
        for (int i = 0; i <oamList.length ; i++) {
            oamList[i] = new OAM();
        }
    }

    /**
     * DMC DMA 将单字节复制到 DMC 单元的采样缓冲区。声音通道使能寄存器（$4015）的 DMC 使能位（第 4 位）置 1 后，DMC DMA 自动发生，该寄存器使用寄存器 $4010-4013 中的当前 DMC 设置启动 DPCM 采样播放。当所有 DPCM 重放都已启用、采样中还有剩余字节且采样缓冲区为空时，将安排 DMC DMA（请参阅 "存储器读取器和输出单元"）。
     * 在常见情况下，DMC DMA 会执行一个停止周期、一个虚拟周期、一个可选对齐周期和一个获取周期。
     *
     * 具体时序取决于 DMC DMA 的类型。有两种类型：加载和重载。加载 DMA 在 $4015 D4 设置后发生，但仅限于采样缓冲区为空的情况。它们计划在写入后的第二个 APU 周期（即第 3 或第 4 个 CPU 周期）内的获取周期上停止 CPU。重新加载 DMA 会在采样缓冲区清空时发生。与加载 DMA 不同的是，重加载 DMA 会在写入周期内停止 CPU 运行。
     *
     * 停止后，DMC DMA 始终执行一个不做任何工作的虚拟周期。如果下一个周期不是获取周期，那么将花费一个周期进行对齐。然后执行 DMA 读取。
     *
     * DMC DMA 通常需要 3 或 4 个周期，具体取决于是否需要对齐。由于加载和重载 DMA 的周期类型不同，加载 DMA 需要 3 个周期，重载 DMA 需要 4 个周期，除非停止延迟的周期数是奇数。不过，错误可能会导致额外的周期；
     *
     * 通过www.DeepL.com/Translator（免费版）翻译
     */
    public class DMA extends DmaDevice {
        private boolean transfer = false;
        private int dma_address = 0;
        private int oamIndex = 0;

        private boolean running=false;
        @Override
        public int fromDeviceRead(int address) {
            return super.fromDeviceRead(address);
        }

        @Override
        public boolean toDeviceWrite(int address, int data) {
            if(address==0x4014){
                transfer = true;
                running = false;
                dma_address = data<<8;
                oamIndex = 0;
                return true;
            }
            return false;
        }

        /**
         * dma传输字节
         * @return  true则下一个时钟还需要传输，false则本次已经传输完毕
         */
        public void transfer(){
            switch (dma_address&0x03){
                case 0x00:
                    oamList[oamIndex].y = fromBusRead(dma_address);
                    break;
                case 0x01:
                    oamList[oamIndex].id = fromBusRead(dma_address);
                    break;
                case 0x02:
                    oamList[oamIndex].attr = fromBusRead(dma_address);
                    break;
                case 0x03:
                    oamList[oamIndex].x = fromBusRead(dma_address);
                    oamIndex++;
                    break;
            }
            dma_address++;
            transfer = (dma_address&0x00FF)==0?false:true;
        }

        public boolean isTransfer(int cpuCycle) {
            if(transfer&&!running){
                running = true;
                if(cpuCycle%2==1) {//奇数CPU周期跳过
                    return false;
                }
            }
            return transfer;
        }
    }

    public DMA getDma(){
        return dma;
    }

    private class OAM implements Serializable{
        private int y;//精灵y坐标
        private int x;//精灵x坐标
        /*
            76543210
            ||||||||
            |||||||+- Bank ($0000 or $1000) of tiles
            +++++++-- Tile number of top of sprite (0 to 254; bottom half gets the next tile)
            该字节类似于 name table，sprite 有 2 种模式：

            8 x 8
            整个 byte 类似于 name table，由 PPUCTR 的 bit 3 选取 bank 之后，加上自身数据 x 16 得到偏移量
            因为 pattern table 表示一个 tile 需要 16 byte, 所以要 * 16

            8 x 16
            该模式下 PUCTRL 的 bit 3 不再起作用，bank 由 bit 0 决定，并且偏移量不再是 x 16，而是 x 32.
            8 * 16 相当于是 两个 tile ， 所以要 * 32.

            下面是一些例子， 表示该字节的各个值映射到的对应内存位置
            $00: $0000-$001F
            $01: $1000-$101F
            $02: $0020-$003F
            $03: $1020-$103F
            $04: $0040-$005F
            [...]
            $FE: $0FE0-$0FFF
            $FF: $1FE0-$1FFF

         */
        private int id;//精灵的tile id
        /*
            00000000
            ||||||++----精灵使用的调色板
            |||+++------未使用
            ||+---------精灵与背景的优先级 0表示精灵在背景前面，1为精灵在背景后面
            |+----------精灵是否水平翻转
            +-----------精灵是否垂直反转
         */
        private int attr;//精灵的调色板id
    }


    /**
     * PPU内部控制寄存器ControlRegister
     */
    private class ControlRegister extends BusDevice{
        private int oamAddress;
        private boolean address_latch = false;//w
        @Override
        public int fromDeviceRead(int address) {
            if(address>=0x2000&&address<0x4000){
                address = 0x2000+address%8;
            }else{
                return  -1;
            }
            int data = 0x00;
            switch (address){
                case 0x2000://control
                    return ctrl.get();
                case 0x2001://mask
                    return mask.get();
                case 0x2002://status
                    //           11100000               00011111
                    data = (status.get()&0xE0)|(ppu_data_buffer&0x1F);//或后面的是噪音
                    status.setValue(Status.vBlank,0);
                    address_latch = false;
                    return data;
                case 0x2003://oam address
                    break;
                case 0x2004://oam data
                    OAM oam = oamList[oamAddress>>2];
                    switch (oamAddress&0x03){
                        case 0:
                            return oam.y;
                        case 1:
                            return oam.id;

                        case 2:
                            return oam.attr;

                        case 3:
                            return oam.x;
                    }
                    break;
                case 0x2005://scroll
                    break;
                case 0x2006:
                    break;
                case 0x2007:
                    data = ppu_data_buffer;
                    ppu_data_buffer = PPU.this.fromBusRead(vramAddress.get());
                    if (vramAddress.get()>0x3F00) {
                        data = ppu_data_buffer;
                    }
                    vramAddress.set(vramAddress.get()+(ctrl.getValue(Control.incrementMode)==0?1:32));
                    return data;
            }
            return 0;
        }

        /**
         * CPU写PPU寄存器
         * @see [https://www.nesdev.org/wiki/PPU_scrolling#PPU_internal_registers]
         * @param address
         * @param data
         * @return
         */
        @Override
        public boolean toDeviceWrite(int address, int data) {
            if(address>=0x2000&&address<0x4000){
                address = 0x2000+address%8;
            }else{
                return false;
            }
            switch (address){
                case 0x2000://ctrl
                    ctrl.set(data);
                    tramAddress.nametable_X = ctrl.getValue(Control.nametable_X);
                    tramAddress.nametable_Y = ctrl.getValue(Control.nametable_Y);
                    break;
                case 0x2001://mask
                    mask.set(data);
                    break;
                case 0x2002://status
                    status.set(data);
                    break;
                case 0x2003://OAM Address
                    oamAddress = data;
                    break;
                case 0x2004://OAM DATA
                    OAM oam = oamList[oamAddress>>2];
                    switch (oamAddress&0x03){
                        case 0:
                            oam.y = data;
                            break;
                        case 1:
                            oam.id = data;
                            break;
                        case 2:
                            oam.attr = data;
                            break;
                        case 3:
                            oam.x = data;
                            break;
                    }

                    break;
                case 0x2005://Scroll
                    /*
                        0x2005 第一次写  w(addressLatch):false(0)
                        t(tramAddress): ....... ...ABCDEF   <-data:ABCDE...
                        x      (fineX):               FGH   <-data:.....FGH
                        w(address_latch):true(1)
                     */
                    if(!address_latch){
                        fine_X = data&0x07;//.....FGH
                        tramAddress.coarse_X = (data>>3)&0x1F;//...ABCDE
                        address_latch = true;
                    /*
                         0x2005 第二次写  w(addressLatch):true(1)
                         t: FGH..AB CDE..... <- data: ABCDEFGH
                         w:                  <- false(0)
                     */
                    }else{
                        tramAddress.fine_Y = data&0x07;//FGH
                        tramAddress.coarse_Y = (data>>3)&0x1F;//...ABCDE
                        address_latch = false;
                    }
                    break;
                case 0x2006://PPU Address
                    if(!address_latch){
                        //设置进来的第一个字节左移到ppu地址2字节地址的高字节位置
                        tramAddress.set((tramAddress.get()&0x00FF)|data<<8);
                        address_latch = true;
                    }else {
                        //设置进来的第二个字节存入地址的低字节位置
                        tramAddress.set((tramAddress.get()&0XFF00)|data);
                        vramAddress.set(tramAddress.get());
                        address_latch = false;
                    }
                    break;
                case 0x2007://PPU Data
                    PPU.this.toBusWrite(vramAddress.get(),data);
                    vramAddress.set(vramAddress.get()+(ctrl.getValue(Control.incrementMode)==0?1:32));//写完后，ppy基于0x2006传入地址自动后移一个字节位置，以便CPU直接向2007再写入数据，而无需再通过两次0x2006下一个字节位置再写出，做到指定一次路径后，连续写入字节
                    break;
            }
            return true;
        }
    }

    public ControlRegister getRegister() {
        return register;
    }


    public boolean isNmi() {
        return nmi;
    }

    public void setNmi(boolean nmi) {
        this.nmi = nmi;
    }

    /**
     * 返回像素应当应用的颜色下标
     *
     * 一个图块的像素是8X8的。每个像素可以应用调色板中的一种颜色，一个调色盘只有4个颜色，因此下标为0，1，2，3
     * 每两个字节构成一行，一共靠16个字节表示8x8像素的图块
     *
     * 以第一行为例:如果第一行两个字节为:01000000 11000000
     *
     * 低字节    高字节
     * 01100000 11000000
     * |________|
     * 这两位二进制组合为10,表示第一行第一个像素应当使用下标2的颜色，即:10
     *
     * 01100000 11000000
     *  |________|
     * 这两位2进制的组合为11,表示第一行第二个像素应当使用下标3的颜色，即11
     *
     * 01100000 11000000
     *   |________|
     *  这两位2进制的组合为01,表示第一行第三个像素应当使用下标1的颜色，即01
     *
     *  01100000 11000000
     *     |________|
     *  这两位2进制的组合为00,表示第一行第四个像素应当使用下标0的颜色，即00
     *
     * @param low   低位1字节
     * @param high 高位1字节
     * @param index 第几个字节
     * @return 返回00,01,10,11四个值的其中一个  对应整数为0,1,2,3
     */
    public int getColorIndex(int low,int high,int index){
        int lowBit = extractBit(low,index);
        int highBit = extractBit(high,index);
        int colorIndex = highBit<<1|lowBit;
        return colorIndex;
    }


    /**
     * 获取给定byte字节value中指定位bitPosition的值
     * @param value 一个字节数据
     * @param bitPosition 希望获取第几位2进制的值
     * @return 返回对应位上的2进制的值，返回的int值为0或1
     *
     * 例如:
     * value:01101101
     * bitPosition:2
     * 表示获取value第3位2进制的值
     * 即:
     * 01101101
     *      ^
     * 此位2进制的值位1.因此返回的int值为1.
     */
    public int extractBit(int value, int bitPosition) {
        // 将1左移bitPosition位，然后与value进行位与运算
        // 这将清除除了我们关心的那一位之外的所有位
        int mask = 1 << bitPosition;
        int bitValue = (value & mask) >> bitPosition;

        return bitValue;
    }
    @Override
    public int fromBusRead(int address) {
        address = address%0x4000;//PPU总线上0x4000-0x10000是0x0000-0x3FFF的镜像
        return super.fromBusRead(address);
    }

    @Override
    public void toBusWrite(int address, int data) {
        address = address%0x4000;//PPU总线上0x4000-0x10000是0x0000-0x3FFF的镜像
        super.toBusWrite(address, data);
    }

    public TVScreen getTvScreen() {
        return tvScreen;
    }

    public void setTvScreen(TVScreen tvScreen) {
        this.tvScreen = tvScreen;
    }

    private void initPalScreen(){
        palScreen = new Color[0x40];
        palScreen[0x00] = new Color(84, 84, 84);
        palScreen[0x01] = new Color(0, 30, 116);
        palScreen[0x02] = new Color(8, 16, 144);
        palScreen[0x03] = new Color(48, 0, 136);
        palScreen[0x04] = new Color(68, 0, 100);
        palScreen[0x05] = new Color(92, 0, 48);
        palScreen[0x06] = new Color(84, 4, 0);
        palScreen[0x07] = new Color(60, 24, 0);
        palScreen[0x08] = new Color(32, 42, 0);
        palScreen[0x09] = new Color(8, 58, 0);
        palScreen[0x0A] = new Color(0, 64, 0);
        palScreen[0x0B] = new Color(0, 60, 0);
        palScreen[0x0C] = new Color(0, 50, 60);
        palScreen[0x0D] = new Color(0, 0, 0);
        palScreen[0x0E] = new Color(0, 0, 0);
        palScreen[0x0F] = new Color(0, 0, 0);

        palScreen[0x10] = new Color(152, 150, 152);
        palScreen[0x11] = new Color(8, 76, 196);
        palScreen[0x12] = new Color(48, 50, 236);
        palScreen[0x13] = new Color(92, 30, 228);
        palScreen[0x14] = new Color(136, 20, 176);
        palScreen[0x15] = new Color(160, 20, 100);
        palScreen[0x16] = new Color(152, 34, 32);
        palScreen[0x17] = new Color(120, 60, 0);
        palScreen[0x18] = new Color(84, 90, 0);
        palScreen[0x19] = new Color(40, 114, 0);
        palScreen[0x1A] = new Color(8, 124, 0);
        palScreen[0x1B] = new Color(0, 118, 40);
        palScreen[0x1C] = new Color(0, 102, 120);
        palScreen[0x1D] = new Color(0, 0, 0);
        palScreen[0x1E] = new Color(0, 0, 0);
        palScreen[0x1F] = new Color(0, 0, 0);

        palScreen[0x20] = new Color(236, 238, 236);
        palScreen[0x21] = new Color(76, 154, 236);
        palScreen[0x22] = new Color(120, 124, 236);
        palScreen[0x23] = new Color(176, 98, 236);
        palScreen[0x24] = new Color(228, 84, 236);
        palScreen[0x25] = new Color(236, 88, 180);
        palScreen[0x26] = new Color(236, 106, 100);
        palScreen[0x27] = new Color(212, 136, 32);
        palScreen[0x28] = new Color(160, 170, 0);
        palScreen[0x29] = new Color(116, 196, 0);
        palScreen[0x2A] = new Color(76, 208, 32);
        palScreen[0x2B] = new Color(56, 204, 108);
        palScreen[0x2C] = new Color(56, 180, 204);
        palScreen[0x2D] = new Color(60, 60, 60);
        palScreen[0x2E] = new Color(0, 0, 0);
        palScreen[0x2F] = new Color(0, 0, 0);

        palScreen[0x30] = new Color(236, 238, 236);
        palScreen[0x31] = new Color(168, 204, 236);
        palScreen[0x32] = new Color(188, 188, 236);
        palScreen[0x33] = new Color(212, 178, 236);
        palScreen[0x34] = new Color(236, 174, 236);
        palScreen[0x35] = new Color(236, 174, 212);
        palScreen[0x36] = new Color(236, 180, 176);
        palScreen[0x37] = new Color(228, 196, 144);
        palScreen[0x38] = new Color(204, 210, 120);
        palScreen[0x39] = new Color(180, 222, 120);
        palScreen[0x3A] = new Color(168, 226, 144);
        palScreen[0x3B] = new Color(152, 226, 180);
        palScreen[0x3C] = new Color(160, 214, 228);
        palScreen[0x3D] = new Color(160, 162, 160);
        palScreen[0x3E] = new Color(0, 0, 0);
        palScreen[0x3F] = new Color(0, 0, 0);

    }


    private class Control extends BitStruct{
        /*
            从低位到高位
            bit0-1，选取 NameTable，00-0x2000, 01-0x2400, 10-0x2800, 11-0x2C00
            bit2，有个专门的寄存器记录访问 VRAM 时的地址，每次访问现存 VRAM，这个值都要增长，0：增长 1 即水平移动，1：增长 32 即纵向移动
            bit3，精灵使用哪个 PatternTable，0：0x0000, 1：0x1000
            bit4，背景使用哪个 PatternTable，0：0x0000, 1：0x1000
            bit5，精灵大小，0：$8\times8$，1：$8\times16$
            bit7，在 V_Blank 开始的时候产生 NMI
        */
        public static final int nametable_X = 0;
        public static final int nametable_Y = 1;
        public static final int incrementMode = 2;
        public static final int patternSprite = 3;
        public static final int patternBackground = 4;
        public static final int spriteSize = 5;
        public static final int slaveMode = 6;
        public static final int generateNMI = 7;
    }

    private class LoopyRegister implements Serializable{
        public int coarse_X = 0;
        public int coarse_Y = 0;

        public int nametable_X = 0;
        public int nametable_Y = 0;
        public int fine_Y = 0;

        public int get(){
            return coarse_X|(coarse_Y<<5)|(nametable_X<<10)|(nametable_Y<<11)|(fine_Y<<12);
        }
        public void set(int data){
            coarse_X = data&0x1F;
            coarse_Y = (data>>5)&0x1F;
            nametable_X = (data>>10)&0x01;
            nametable_Y = (data>>11)&0x01;
            fine_Y = (data>>12)&0x07;
        }
    }

    private class Mask extends BitStruct{
        /*
            mask 要屏蔽什么东西，所以这个寄存器就是来控制哪些渲染哪些不渲染：
            bit0，0：显示正常颜色，1：显示黑白图像
            bit1，1：渲染背景最左侧 8 列像素，0：不渲染
            bit2，当精灵位于屏幕最左侧时，1：渲染精灵左侧 8 列像素，0：不渲染
            bit3，1：渲染背景，0：不渲染
            bit4，1：渲染精灵，0：不渲染
        */
        public static final int grayscale = 0;
        public static final int showLeftBackground = 1;
        public static final int showLeftSprites = 2;
        public static final int showBackground = 3;
        public static final int showSprites = 4;
        public static final int emphasizeRed = 5;
        public static final int emphasizeGreen = 6;
        public static final int emphasizeBlue = 7;
    }

    private class Status extends BitStruct{
        public static final int unused0 = 0;
        private static final int unused1 = 1;
        private static final int unused2 = 2;
        private static final int unused3 = 3;
        private static final int unused4 = 4;
        private static final int spriteOverflow = 5;
        private static final int sprite0Hit = 6;
        private static final int vBlank = 7;
    }


    public class BitStruct implements Serializable {
        private int data;
        public void set(int value){
            this.data = value&0xFF;
        }
        public void setValue(int flag,int value){
            data=value==1?data|1<<flag:data&~(1<<flag);
        }
        public int getValue(int flag){
            return (data&(1<<flag))>0?1:0;
        }
        public int get(){
            return data&0xFF;
        }
    }

    /*
        非正常模拟，实际上Mapper的scanline计数器不是由PPU直接调用
     */
    private Mapper mapper;

    public Mapper getMapper() {
        return mapper;
    }

    public void setMapper(Mapper mapper) {
        this.mapper = mapper;
    }
}
